Flutter – Flameでゲーム作成 (キャラクターの移動)
2023.07.23
この記事は最終更新日から1年以上が経過しています。

Flameは、2Dゲーム開発に特化したFlutterの拡張パッケージとなります。Flameを使用することで、簡単に美しい2Dゲームやインタラクティブなアプリケーションを作成することができます。
Flameは、Flutterと組み合わせて使用することで、高速でパフォーマンスの良いゲームを開発することができます。また、シンプルなAPIと使いやすさが特徴であり、ゲーム開発者にとって便利なツールとなっています。
Flameは、以下のような機能を提供しています。
- ゲームループの管理
- 統合タッチイベントのサポート
- アニメーションの作成
- 物理エンジンのサポート
- カメラの制御
- パーティクルエフェクトの作成
etc..
Flameは、FlutterのWidgetとして実装されており、Flutterの他のWidgetと同様に、簡単に使用することができます。FlutterのWidgetの知識があれば、Flameを使用することは簡単です。
Flameの開発には、Dart言語を使用します。Dartは、Flutterの開発に使用されるプログラミング言語であり、JavaScriptやJavaといった言語に似た構文を持っています。Dartは、静的型付け言語であり、高速で安全なコードを書くことができます。
FlutterとFlameを使用することで、美しい2Dゲームを作成することができます。Flutterは、Android、iOS、Webなどの多くのプラットフォームをサポートしており、Flameはそれらのプラットフォームで動作します。Flameを使用することで、Flutterを使用した2Dゲームの作成がより簡単になります。
公式サイト
公式ドキュメント(1.8.0)
https://docs.flame-engine.org/1.8.0/
Pub.Dev(1.8.0)
https://pub.dev/packages/flame/install
といった感じで、ざっっと概要は述べたところで、実際に使っていきましょう。
一つのゲーム作成までと行きたいところですが、そうなると結構ボリューミィになってしまうので、今回はとりあえず、キャラクターを表示し動かすところまでやっていきましょう。
では、早速パッケージを追加して行きましょう。
pubコマンドを用いて、pub devよりパッケージを追加します。
flutter pub add flame
pubspec.yaml
dependencies: flutter: sdk: flutter flame: 1.6.0 flutter: assets: - assets/images/
追加できていればオッケー。
手動で記述し、パッケージを追加することも可能です。
$ flutter pub get
main.dart
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import '〇〇.dart';
void main() {
final game = 〇〇Game();
runApp(
GameWidget(game: game),
);
}
flameパッケージ追加後、importを行い、GameWidgetを使用して内容を作成していきます。
実際のソースはこの様な形となりました。
import 'package:flame/flame.dart';
import 'package:flutter/material.dart';
import 'main_game_page.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const App());
}
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'SpriteAnime',
home: MainGamePage(),
);
}
}
ページ単位で切り出せる様に、main_game_page.dartを設け、StatefulWidgetとして作成し、実際のflameのゲーム処理に関しては、更にgame.dartとファイルの切り出し。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flame/palette.dart';
import 'package:flame/game.dart';
import 'helpers/joypad.dart';
import 'game.dart';
class MainGamePage extends StatefulWidget {
const MainGamePage({Key? key}) : super(key: key);
@override
MainGameState createState() => MainGameState();
}
class MainGameState extends State<MainGamePage> {
MainGame game = MainGame();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color.fromRGBO(0, 0, 0, 1),
body: Stack(
children: [
GameWidget(game: game),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(32.0),
child:
Joypad(onDirectionChanged: game.onJoyPadDirectionChanged),
),
)
],
)
);
}
}
枠組みはこの様な形で、キャラクターを動かすために、player componentを作成していきます。
キャラクターを動かす
今回のゴールとして、キャラクターを表示し、移動させるところまで行いたいと思います。イメージは以下の様にウィンドウ上にキャラクターが表示し、2D RPG的な動きで上下左右キーボードの操作で移動できる形となります。

説明はいいから、ソースを先に確認されたい方は、サンプルのソースはこちらとなります。
https://github.com/flame-games/player_move
git cloneを行い、fluter runコマンドで実行することができます。
スプライトの読み込み
上方向にキャラクターが移動する際は、上方向の表示、下方向の場合は下向きの表示など各表示似合わせてキャラクターも表示するように、まずはキャラクターのスプライト画像の読み込みを行います。使用したスプライト画像はこちらのものとなります。
キャラクタースプライト画像
Retro Character Sprite Sheet

スプライト読み込み
components/player.dart ファイルを作成。
今回は、flameのSpriteAnimationComponentを用いて作成するので、SpriteAnimationComponentを継承したPlayer Classを作成。
components/player.dart
class Player extends SpriteAnimationComponent with HasGameRef {
キャラクターのアニメーションを行う、_loadAnimations関数を作成。
onLoadをoverrideし、_loadAnimations関数を実行します。
@override
Future<void> onLoad() async {
super.onLoad();
await _loadAnimations().then((_) => {animation = _standingAnimation});
}
Future<void> _loadAnimations() async {
final spriteSheet = SpriteSheet(
image: await gameRef.images.load('sp_player.png'),
srcSize: Vector2(84.0, 110.0),
);
....
FlameのSpriteSheetを用いて画像を読み込みます。
この際、Player Classでwith HasGameRefを行うことによって、gameRefを使用可能となっていますので、gameRef.images.loadで対象のスプライト画像を読み込みし、スプライト画像をクリッピングするサイズの指定を行います。
スプライトアニメーション
スプライトアニメーションが行われるように、アニメーションの設定を行います。
スプライト画像のキャラクターは、1行目が下向き(Down)、2行目が上向き(Up)…という形となっておりますので、それに合わせ設定。
_runDownAnimation = spriteSheet.createAnimation(row: 0, stepTime: _animationSpeed, to: 4);
spriteSheetのcreateAnimationメソッドで設定を行います。
row 0は、スプライト画像の1行目に該当し、stepTimeは スプライト画像が4stepで用意されているので4を指定。これを _runDownAnimation変数として扱います。
その他の方向も作成します。
アニメーションの速度とキャラクターの移動速度を_playerSpeed、_animationSpeedの定数として用意しておきます。
final double _playerSpeed = 300.0; final double _animationSpeed = 0.15; ... late final SpriteAnimation _runDownAnimation; ...
キャラクターの移動
サンプルにはJoypadによる実装も入っておりますが、今回の説明としてはキーボード入力のみとしておきます。
キーボードイベントが取得できるように、game.dartファイルを作成していきます。
MainGame ClassはFlameGameを継承し、KeyboardEventsも扱えるように withで指定しておきます。
game.dart
class MainGame extends FlameGame with KeyboardEvents {
KeyboardEventsのonKeyEventをoverrideします。RawKeyEventとSet<LogicalKeyboardKey>を受け取ります。
@override
KeyEventResult onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
キャラクターの方向(キーボードが押されている方向)のDirection enumを作成。
enum Direction { up, down, left, right, none }
今回は、上: w、左: a、右: d、下: sのキーに割り当てたいので、event.logicalKeyがLogicalKeyboardKeyのkeyAやkeyDと同等かでkeyDirection変数に格納しております。
キーアップする(離す)ことで、キャラクター移動も止め、アイドルアニメーション割当ができるように、isKeyDownとkeyDirectionで判定します。
キーアップ(押されていない状態)だと、_player.directionにはDirection.noneが割当てられ、それ以外はそれぞれの方向が割当てられます。
game.dart
@override
KeyEventResult onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
final isKeyDown = event is RawKeyDownEvent;
Direction? keyDirection = null;
if (event.logicalKey == LogicalKeyboardKey.keyA) {
keyDirection = Direction.left;
} else if (event.logicalKey == LogicalKeyboardKey.keyD) {
keyDirection = Direction.right;
} else if (event.logicalKey == LogicalKeyboardKey.keyW) {
keyDirection = Direction.up;
} else if (event.logicalKey == LogicalKeyboardKey.keyS) {
keyDirection = Direction.down;
}
if (isKeyDown && keyDirection != null) {
_player.direction = keyDirection;
} else if (_player.direction == keyDirection) {
_player.direction = Direction.none;
}
return super.onKeyEvent(event, keysPressed);
}
再び、Player componentを修正。
ゲームサイクルのupdate内で、キャラクター移動に関することを監視したいので、
updateをoverrideし、キャラクター移動に関する関数、movePlayerを実行します。
movePlayer関数内で、playerのdirectionをチェックし、Direction.upやDirection.downなどenumと比較し、それぞれの向きによって用意した関数を引数deltaを渡し実行します。
また、animation = _runUpAnimation;で、Player自身のanimationを最初の方に作成した各方向それぞれ方向別に用意した、spriteSheetのアニメーションを渡し、キャラクターアニメーションも実行します。
components/player.dart
@override
void update(double delta) {
super.update(delta);
movePlayer(delta);
}
void movePlayer(double delta) {
switch (direction) {
case Direction.up:
animation = _runUpAnimation;
moveUp(delta);
break;
case Direction.down:
animation = _runDownAnimation;
moveDown(delta);
break;
case Direction.left:
animation = _runLeftAnimation;
moveLeft(delta);
break;
case Direction.right:
animation = _runRightAnimation;
moveRight(delta);
break;
case Direction.none:
animation = _standingAnimation;
break;
}
}
上であれば、moveUp関数を実行。
Player Class(SpriteAnimationComponent)のpositionを変更していきます。
上方向であれば、y座標の値がマイナスとなるので、deltaと_playerSpeedを用いてy座標を減算し、Player自身を上に移動させていきます。
void moveUp(double delta) {
position.add(Vector2(0, delta * -_playerSpeed));
}
キャラクターを実際に画面表示するには、Player componentをMainGame classのプロパティとして保持、onLoad時に addします。
game.dart
final Player _player = Player();
@override
Future<void> onLoad() async {
super.onLoad();
add(_player);
}
これで一通り必要最低限のキャラクター移動の実装ができましたので、動かしてみましょう。
と、Flatter、Flameを色々触っていたのが4ヶ月前と、時間の流れの速さを感じており、振り返りがてら備忘録としてFlatter、Flame周りについて書いて行こうかと思っております。
4ヶ月前は Flameのバージョンも1.6.0が最新だったのですが、現在では1.8.1が最新となっており、こちらの開発の速さも感じれます。
開発も活発に行われていてこれからも期待できそうなFlutterのゲームエンジンとなっております。
次回は、マップ移動やゲーム作成について触れられればと思っております。
ではではぁ。











